<!DOCTYPE html>
<html>
  <head>
    <title>
      Test decodeAudioData promises
    </title>
    <script src="../../resources/testharness.js"></script>
    <script src="../../resources/testharnessreport.js"></script>
    <script src="../resources/audit.js"></script>
  </head>
  <body>
    <script id="layout-test-code">
      // The functionality of decodeAudioData() is orthogonal to the type and
      // the state of AudioContext. So we use the online context here and any
      // resampling of the file is okay for this test.
      let context = new AudioContext();

      // Test file URLs.
      let validAudioFileUrl = '../resources/media/24bit-44khz.wav';
      let invalidAudioFileUrl = '../resources/media/invalid-audio-file.txt';

      // Global storage for array buffers from XHR.
      let validArrayBuffer;
      let invalidArrayBuffer;

      // Decoded data from validAudioFile.
      let referenceDecodedAudioBuffer;

      let audit = Audit.createTaskRunner();

      // Preload ArrayBuffer and the reference AudioBuffer from URLs.
      audit.define('preload-arraybuffer', (task, should) => {
        Promise
            .all([
              should(
                  Audit.loadFileFromUrl(validAudioFileUrl),
                  'Loading valid audio file')
                  .beResolved(),
              should(
                  Audit.loadFileFromUrl(invalidAudioFileUrl),
                  'loading invalid audio file')
                  .beResolved()
            ])
            .then((arrayBuffers) => {
              validArrayBuffer = arrayBuffers[0];
              invalidArrayBuffer = arrayBuffers[1];
            })
            .then(() => task.done());
      });

      // Decode a valid encoded file and verify that the promise succeeds
      // correctly.
      audit.define('decode-valid-file', (task, should) => {
        // Note that the order of completion for each promise is undefined and
        // we do not care about it in this test.
        Promise
            .all([
              // Do not use the original arrayBuffers for decoding; decode a
              // copy because decodeAudioData will detach the buffers.
              should(
                  context.decodeAudioData(validArrayBuffer.slice(0)),
                  'Decoding a valid audio file')
                  .beResolved()
                  .then(buffer => referenceDecodedAudioBuffer = buffer),
              should(
                  context.decodeAudioData(invalidArrayBuffer.slice(0)),
                  'Decoding an invalid audio file')
                  .beRejectedWith('EncodingError'),
              should(context.decodeAudioData(null), 'Decoding null AudioBuffer')
                  .beRejected()
            ])
            .then(() => task.done());
      });

      // Decode a valid file and verify that the promise is fulfilled and the
      // successCallback is invoked and both have identical decoded audio
      // buffers.
      audit.define('promise-and-success-callback', (task, should) => {
        let bufferByCallback;
        let bufferByPromise;

        // Use one callback for success and error. |callbackArg| is a parameter
        // for callback functions; it is a decoded audio buffer for success case
        // and an error object for failure case.
        let successOrErrorCallback = (callbackArg) => {
          should(
              callbackArg instanceof AudioBuffer,
              'Decoding valid file by callback function')
              .message(
                  'successCallback invoked correctly',
                  'errorCallback incorrectly invoked with ' + callbackArg);
          bufferByCallback = callbackArg;
        };

        // Step 1: Decode a file with callback functions.
        let step1 = context.decodeAudioData(
            validArrayBuffer.slice(), successOrErrorCallback,
            successOrErrorCallback);

        // Step 2: Then decode a file with promise pattern.
        let step2 = should(step1, 'Decoding a file via promise')
                        .beResolved()
                        .then((audioBuffer) => {
                          bufferByPromise = audioBuffer;
                        });

        // Step 3: compare two buffers from Step 1 and Step 2.
        step2.then(() => {
          should(
              bufferByCallback === bufferByPromise,
              'Two buffers decoded by callback function and promise')
              .message('are identical', 'are different');
          task.done();
        });
      });

      // Decode an invalid file and verify that the promise is rejected and the
      // errorCallback is invoked.
      audit.define('promise-and-error-callback', (task, should) => {
        let successOrErrorCallback = (callbackArg) => {
          should(
              callbackArg instanceof Error,
              'Decoding invalid file with promise and callback:')
              .message(
                  'errorCallback invoked correctly with ' + callbackArg,
                  'successCallback should not have invoked');
        };

        let decodeAudioDataPromise = context.decodeAudioData(
            invalidArrayBuffer.slice(), successOrErrorCallback,
            successOrErrorCallback);

        should(decodeAudioDataPromise, 'decodeAudioData promise')
            .beRejected('EncodingError')
            .then(() => task.done());
      });

      // decodeAudioData() should be functional even after the associated
      // context is closed.
      audit.define('decoding-on-closed-context', (task, should) => {
        // Use one handler for resolve and reject. |promiseArg| is a parameter
        // for handlers; it is a decoded audio buffer for success case and an
        // error object for failure case.
        let resolveOrReject = (promiseArg) => {
          let didDecode = promiseArg instanceof AudioBuffer;

          if (didDecode) {
            // Compare two decoded AudioBuffers.
            let actual = promiseArg;
            let expected = referenceDecodedAudioBuffer;
            should(actual.length, 'Decoded buffer length (frames)')
                .beEqualTo(expected.length);
            should(actual.duration, 'Decoded buffer duration (sec)')
                .beEqualTo(expected.duration);
            should(actual.sampleRate, 'Decoded buffer sample rate (Hz)')
                .beEqualTo(expected.sampleRate);
            should(
                actual.numberOfChannels, 'Number of channels in decoded buffer')
                .beEqualTo(expected.numberOfChannels);
            for (let c = 0; c < expected.numberOfChannels; ++c) {
              let actualChannelData = actual.getChannelData(c);
              let expectedChannelData = expected.getChannelData(c);
              should(actualChannelData, 'Decoded buffer channel #' + c)
                  .beEqualToArray(
                      expectedChannelData, 'the expected channel #' + c);
            }
            should(task.state, 'The buffer')
                .message(
                    'correctly decoded after the context has been closed',
                    'decoding succeeded but the data is incorrect');
          }

          should(
              didDecode, 'Decoding ArrayBuffer after context has been closed')
              .message('completed successfully', 'failed : ' + promiseArg);
        };

        let onlineContext = new AudioContext();
        onlineContext.close()
            .then(() => {
              return context.decodeAudioData(validArrayBuffer);
            })
            .then(resolveOrReject, resolveOrReject)
            .then(() => {
              task.done();
            });
      });

      audit.run();
    </script>
  </body>
</html>
